﻿//  -------------------- iSharePoint -------------------- //
// 
//  Company:	IT Complex, LLC
//  Project:	Smart.Common
//  File name:	CacheFactory.cs
//  Developer:	Solomatov Igor
//  Created:	15.04.2012
// 
//  -------------------- iSharePoint -------------------- //

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading;
using System.Web;
using System.Web.Caching;

namespace Smart.Common
{
	/// <summary>
	/// Cache factory
	/// </summary>
	[Localizable(false)]
	public class CacheFactory : ICacheFactory
	{
		/// <summary>
		/// Cache store
		/// </summary>
		public Cache MainCache
		{
			get { return Smart.SCache.GetCache(); }
		}

		/// <summary>
		/// Request store
		/// </summary>
		public IDictionary RequestStore
		{
			get { return Smart.RequestStore.GetStore(); }
		}

		/// <summary>
		/// Cache clearing notification
		/// </summary>
		public event Action CacheClearing;

		private readonly ReaderWriterLockSlim _syncLockMainCache = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);

		public TimeSpan DefaultTimeout
		{
			get { return _defaultTimeout; }
		}

		public TimeSpan NoSlidingExpiration
		{
			get { return _noSlidingExpiration; }
		}

		public CacheItemPriority DefaultPriority
		{
			get { return _defaultPriority; }
		}

		public TimeSpan ShortTimeout
		{
			get { return _shortTimeout; }
		}

		public TimeSpan LongTimeout
		{
			get { return _longTimeout; }
		}

		private readonly TimeSpan _defaultTimeout = new TimeSpan(0, 0, 15, 0);
		private readonly TimeSpan _noSlidingExpiration = Cache.NoSlidingExpiration;
		private readonly CacheItemPriority _defaultPriority = CacheItemPriority.BelowNormal;

		private readonly TimeSpan _shortTimeout = new TimeSpan(0, 0, 5, 0);
		private readonly TimeSpan _longTimeout = new TimeSpan(0, 0, 45, 0);

		public string[] RemoveKeysText =
			new[]
				{
					"<Where>", "</Where>", "<OrderBy>", "</OrderBy>", "<GroupBy>", "</GroupBy>",
					"<FieldRef Name=", "<Value Type=", "</Value>", "<Webs", "<Lists",
					"</", "<", ">", "/>", "</",
					"\"Integer\"", "\"Text\"", "\"Counter\"", "\"Lookup\"", " LookupId=\"TRUE\""
				};

		public string GetKey(object[] preValues, bool siteDependent, bool webDependent, bool userDependent, bool urlDependent, string query, object[] postValues)
		{
			var sb = new StringBuilder();

			if (preValues != null)
			{
				foreach (var value in preValues)
				{
					if (value == null) continue;

					sb.Append(value);
					sb.Append("-");
				}
			}

			if (siteDependent)
			{
				sb.Append(SPH.GetSite().ID.GetHashCodeX());
				sb.Append("-");
			}

			if (webDependent)
			{
				sb.Append(SPH.GetWeb().ID.GetHashCodeX());
				sb.Append("-");
			}

			if (userDependent)
			{
				sb.Append(SPH.GetCurrentUser().ID.ToStringX());
				sb.Append("-");
			}

			if (urlDependent)
			{
				sb.Append(SPH.GetCurrentUrl().ToLowerInvariant().GetHashCodeX());
				sb.Append("-");
			}

			if (!string.IsNullOrEmpty(query))
			{
				var queryLower = query.ToLower();

				sb.Append(queryLower.GetHashCodeX());
				sb.Append(queryLower.RemoveAny(RemoveKeysText).GetHashCodeX());

				sb.Append("-");
			}

			if (postValues != null)
			{
				foreach (var value in postValues)
				{
					if (value == null) continue;

					sb.Append(value);
					sb.Append("-");
				}
			}

			return sb.Length > 0 ? sb.ToString(0, sb.Length - 1) : string.Empty;
		}

		public string[] GetKeys()
		{
			string[] res;

			_syncLockMainCache.EnterReadLock();

			try
			{
				res =
					MainCache
						.Cast<DictionaryEntry>()
						.Select(de => de.Key.SToStr(null))
						.Where(key => !string.IsNullOrEmpty(key))
						.ToArray();
			}
			finally
			{
				_syncLockMainCache.ExitReadLock();
			}

			return res;
		}

		public object Get(string key, object Default)
		{
			object cv;

			_syncLockMainCache.EnterReadLock();

			try
			{
				cv = MainCache[key];

#if TRACE_CACHE
				if (CV == null)
					H.LT("-- SPCache    miss {0}!", Key);
				else
					H.LT("++ SPCache    HIT  {0}!", Key);
#endif
			}
			finally
			{
				_syncLockMainCache.ExitReadLock();
			}

			return (cv ?? Default);
		}

		public void Set(string key, object value, TimeSpan timeout, CacheItemPriority priority)
		{
			if (value == null)
			{
				Remove(key);
			}
			else
			{
				_syncLockMainCache.EnterWriteLock();

				try
				{
					MainCache.Insert(key, value, null, DateTime.Now.Add(timeout), Cache.NoSlidingExpiration, priority, null);
				}
				finally
				{
					_syncLockMainCache.ExitWriteLock();
				}
			}
		}

		public object Get(string key, TimeSpan absoluteTimeout, TimeSpan slidingExpiration, CacheItemPriority priority, Func<string, object> loader)
		{
			object cv;

			_syncLockMainCache.EnterUpgradeableReadLock();

			try
			{
				cv = MainCache[key];

#if TRACE_CACHE
				if (CV == null)
					H.LT("-- SPCache    miss {0}!", Key);
				else
					H.LT("++ SPCache    HIT  {0}!", Key);
#endif

				if (cv == null)
					Set(key, cv = loader(key), absoluteTimeout, slidingExpiration, priority);
			}
			finally
			{
				_syncLockMainCache.ExitUpgradeableReadLock();
			}

			return cv;
		}

		public void Set(string key, object value, TimeSpan absoluteTimeout, TimeSpan slidingExpiration, CacheItemPriority priority)
		{
			if (value == null)
			{
				Remove(key);
			}
			else
			{
				if (slidingExpiration.TotalSeconds <= 1.0)
					slidingExpiration = Cache.NoSlidingExpiration;

				var absoluteExpiration = DateTime.Now.Add(absoluteTimeout);

				if (slidingExpiration != Cache.NoSlidingExpiration)
					absoluteExpiration = Cache.NoAbsoluteExpiration;

				_syncLockMainCache.EnterWriteLock();

				try
				{
					MainCache.Insert(key, value, null, absoluteExpiration, slidingExpiration, priority, null);
				}
				finally
				{
					_syncLockMainCache.ExitWriteLock();
				}
			}
		}

		public void Remove(string key)
		{
			_syncLockMainCache.EnterWriteLock();

			try
			{
				MainCache.Remove(key);
			}
			finally
			{
				_syncLockMainCache.ExitWriteLock();
			}
		}

		public void RemoveKeys(string keyStartWith)
		{
			_syncLockMainCache.EnterWriteLock();

			try
			{
				var cache = MainCache;

				var keys = new List<string>();

				foreach (DictionaryEntry de in cache)
				{
					var key = de.Key.SToStr();

					if (!string.IsNullOrEmpty(key) && key.StartsWith(keyStartWith, StringComparison.OrdinalIgnoreCase))
						keys.Add(key);
				}

				foreach (var key in keys)
				{
					cache.Remove(key);
				}
			}
			finally
			{
				_syncLockMainCache.ExitWriteLock();
			}
		}

		public void Clear()
		{
			_syncLockMainCache.EnterWriteLock();

			try
			{
				if (CacheClearing != null)
					CacheClearing();

				var cache = MainCache;

				var keys = new List<string>();

				foreach (DictionaryEntry de in cache)
				{
					var key = de.Key.SToStr();

					if (!string.IsNullOrEmpty(key))
						keys.Add(key);
				}

				foreach (var key in keys)
				{
					cache.Remove(key);
				}
			}
			finally
			{
				_syncLockMainCache.ExitWriteLock();
			}

			try
			{
				var cache = RequestStore;

				var keys = new List<string>();

				foreach (DictionaryEntry de in cache)
				{
					var key = de.Key.SToStr();

					if (!string.IsNullOrEmpty(key) && key.StartsWith(Const.Cache.RequestKey, StringComparison.OrdinalIgnoreCase))
						keys.Add(key);
				}

				foreach (var key in keys)
				{
					cache.Remove(key);
				}
			}
			finally
			{
			}
		}

		/// <summary>
		/// Read request store and if needed load value
		/// </summary>
		public object GetRequestItem(string key, Func<object> loader)
		{
			if (key.IsNullOrEmpty() || loader == null)
				throw new ArgumentException();

			object res;

			var fullkey = Const.Cache.RequestKey + key.ToLower();

			var cache = RequestStore;

			if (cache.Contains(fullkey))
			{
				res = cache[fullkey];
			}
			else
			{
				cache[fullkey] = res = loader();
			}

			return res;
		}

		/// <summary>
		/// Set request store value
		/// </summary>
		public void SetRequestItem(string key, object value)
		{
			if (key.IsNullOrEmpty())
				throw new ArgumentException();

			var fullkey = Const.Cache.RequestKey + key.ToLower();

			if (value == null)
				RequestStore.Remove(fullkey);
			else
				RequestStore[fullkey] = value;
		}

		/// <summary>
		/// <para>Read request store with loader</para>
		/// </summary>
		public TValueType ReadRequestStore<TKeyType, TValueType>(string keyName, TKeyType comparand, string valueName, Func<TKeyType, TValueType> loader)
		{
			TValueType res;

			keyName = Const.Cache.RequestKey + keyName;
			valueName = Const.Cache.RequestKey + valueName;

			var requestStore = RequestStore;

			if (requestStore.Contains(keyName) && object.Equals((TKeyType)requestStore[keyName], comparand))
			{
				res = (TValueType)requestStore[valueName];
			}
			else
			{
				requestStore[keyName] = comparand;
				requestStore[valueName] = res = loader(comparand);
			}

			return res;
		}
	}
}